package com.gitee.easyopen.sdk;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.UUID;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.gitee.easyopen.sdk.resp.ErrorResp;
import com.gitee.easyopen.sdk.util.AESUtil;
import com.gitee.easyopen.sdk.util.ClassUtil;
import com.gitee.easyopen.sdk.util.JsonUtil;
import com.gitee.easyopen.sdk.util.MD5Util;
import com.gitee.easyopen.sdk.util.PostUtil;
import com.gitee.easyopen.sdk.util.RSAUtil;
import com.gitee.easyopen.sdk.util.SignUtil;

/**
 * 请求客户端，创建一个即可。
 * 
 * @author hc.tang
 *
 */
public class OpenClient {

    private static final String AND = "&";
    private static final String EQ = "=";
    private static final String UTF8 = "UTF-8";
    private static final String ZH_CN = "zh-CN";

    private static final String ACCEPT_LANGUAGE = "Accept-Language";
    private static final String AUTHORIZATION = "Authorization";
    private static final String PREFIX_BEARER = "Bearer ";
    
    private static final String METHOD_GET = "get";
    private static final String METHOD_POST = "post";
    
    

    private static String publicKey; // 公钥数据
    private static volatile String randomKey; // 随机码

    private String url;
    private String appKey;
    private String secret;
    private String lang = ZH_CN;

    private String appKeyName = "app_key";
    private String signName = "sign";
    private String dataName = "data";

    public OpenClient(String url, String appKey, String secret, String lang) {
        this(url, appKey, secret);
        this.lang = lang;
    }

    public OpenClient(String url, String appKey, String secret) {
        this.url = url;
        this.appKey = appKey;
        this.secret = secret;

        handshake(url);
    }

    private synchronized void handshake(String url) {
        if (this.getRequestMode() == RequestMode.ENCRYPT) {
            initRandomKey();
            initPublicKey();
            try {
                // 传递随机数
                String randomKeyEncrypted = createRandomKeyByPublicKey();
                url = url + "/handshake";
                String json = PostUtil.postText(url, randomKeyEncrypted);
                checkResp(json);
            } catch (Exception e) {
                e.printStackTrace();
                throw new RuntimeException("握手失败", e);
            }
        }
    }

    private static void initRandomKey() {
        if (randomKey != null) {
            return;
        }
        randomKey = MD5Util.encrypt16(UUID.randomUUID().toString());
    }

    private static void initPublicKey() {
        if (publicKey != null) {
            return;
        }
        InputStream input = OpenClient.class.getClassLoader().getResourceAsStream("pub.key");
        if (input != null) {
            InputStreamReader in = null;
            StringWriter writer = null;
            try {
                in = new InputStreamReader(input, "UTF-8");
                writer = new StringWriter();
                char[] buffer = new char[4096];
                int EOF = -1;
                int n;
                while (EOF != (n = in.read(buffer))) {
                    writer.write(buffer, 0, n);
                }
                publicKey = writer.toString();
            } catch (Exception e) {
            } finally {
                try {
                    in.close();
                    writer.close();
                } catch (IOException e) {
                }
            }
        }
        if (publicKey == null || "".equals(publicKey)) {
            throw new RuntimeException("加载公钥失败");
        }
    }

    private static String createRandomKeyByPublicKey() throws Exception {
        return RSAUtil.encryptByPublicKey(randomKey, RSAUtil.getPublicKey(publicKey));
    }

    private static void checkResp(String resp) throws Exception {
        ApiResult result = JsonUtil.parseOject(resp, ApiResult.class);
        if (!"0".equals(result.getCode())) {
            throw new RuntimeException(result.getMsg());
        }

        String data = String.valueOf(result.getData());
        String desStr = RSAUtil.decryptByPublicKey(data, RSAUtil.getPublicKey(publicKey));

        String content = AESUtil.decryptFromHex(desStr, randomKey);
        // 一致
        boolean same = "0".equals(content);

        if (!same) {
            throw new RuntimeException("数据传输有误");
        }
    }

    public <T> T request(BaseReq<T> request) {
        return this.requestWithJwt(request, null);
    }

    public <T> T requestWithJwt(BaseReq<T> request, String jwt) {
        return doRequest(request, jwt, null, METHOD_POST);
    }

    /**
     * get请求
     * @param request
     * @return
     */
    public <T> T requestGet(BaseReq<T> request) {
        return this.requestGet(request, null);
    }

    /**
     * get请求
     * @param request
     * @param jwt jwt
     * @return
     */
    public <T> T requestGet(BaseReq<T> request, String jwt) {
        return this.doRequest(request, jwt, null, METHOD_GET);
    }

    /**
     * 上传文件
     * 
     * @param request
     * @param files
     *            上传的文件
     * @return
     * @throws IOException
     */
    public <T> T requestFile(BaseReq<T> request, List<UploadFile> files) throws IOException {
        return doRequest(request, null, files, METHOD_POST);
    }

    private <T> T doRequest(BaseReq<T> request, String jwt, List<UploadFile> files,String method) {
        Map<String, Object> params = ClassUtil.convertObj2Map(request);
        this.appendFileMd5(params, files);
        String data = JsonUtil.toJson(params.get(dataName));
        if (data == null || "".equals(data)) {
            data = "{}";
        }
        try {
            data = URLEncoder.encode(data, UTF8);
            params.put(dataName, data);
        } catch (UnsupportedEncodingException e) {
        }
        params.put(appKeyName, this.appKey);
        Map<String, String> header = buildHeader();
        if (jwt != null) {
            header.put(AUTHORIZATION, PREFIX_BEARER + jwt);
        }
        String body = METHOD_GET.equalsIgnoreCase(method) 
                ? this.reqGet(url, params, header)
                : this.post(url, params, header, files);
        T t = (T) JsonUtil.parseOject(body, request.buildRespClass());
        return t;
    }

    private String reqGet(String url, Map<String, Object> params, Map<String, String> header) {
        String body = null;
        StringBuilder queryString = new StringBuilder();
        params = buildSignTypeParam(params);
        Set<Entry<String, Object>> set = params.entrySet();
        try {
            for (Entry<String, Object> entry : set) {
                queryString.append(AND).append(entry.getKey()).append(EQ)
                        .append(URLEncoder.encode(String.valueOf(entry.getValue()), UTF8));

            }
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        
        url = url + "?" + queryString.toString().substring(1);
        
        try {
            body = PostUtil.get(url, header);
            return body;
        } catch (IOException e) {
            ErrorResp result = new ErrorResp();
            result.setCode("-1");
            result.setMsg(e.getMessage());
            return JsonUtil.toJson(result);
        }
        
    }

    private void appendFileMd5(Map<String, Object> params, List<UploadFile> files) {
        if (files != null) {
            for (UploadFile uploadFile : files) {
                params.put(uploadFile.getName(), uploadFile.getMd5());
            }
        }
    }

    /**
     * 发送请求
     * 
     * @param url
     *            请求连接
     * @param params
     *            请求参数
     * @return 服务端返回的内容
     */
    private String post(String url, Map<String, Object> params, Map<String, String> header, List<UploadFile> files) {
        String jsonParam = null;
        RequestMode requestMode = this.getRequestMode();

        if (requestMode == RequestMode.SIGNATURE) {
            Map<String, Object> param = buildSignTypeParam(params);
            jsonParam = JsonUtil.toJson(param);
        } else if (requestMode == RequestMode.ENCRYPT) {
            try {
                jsonParam = this.buildRsaContent(params);
                this.encryptHeader(header);
                url = url + "/ssl";
            } catch (Exception e) {
                throw new RuntimeException("构建参数失败", e);
            }
        } else {
            throw new RuntimeException("找不到请求模式:" + requestMode.name());
        }

        return this.doPost(url, jsonParam, header, files);
    }

    // 加密header
    private void encryptHeader(Map<String, String> header) throws Exception {
        Set<Entry<String, String>> entrySet = header.entrySet();
        for (Entry<String, String> entry : entrySet) {
            String key = entry.getKey();
            String value = entry.getValue();

            value = doAes(value, randomKey);

            header.put(key, value);
        }
    }

    private Map<String, String> buildHeader() {
        Map<String, String> header = new HashMap<String, String>();
        header.put(ACCEPT_LANGUAGE, lang);
        return header;
    }

    private String buildRsaContent(Map<String, Object> params) throws Exception {
        return doAes(JsonUtil.toJson(params), randomKey);
    }

    protected String doAes(String content, String key) throws Exception {
        return AESUtil.encryptToHex(content, key);
    }

    private Map<String, Object> buildSignTypeParam(Map<String, Object> params) {
        String sign = null;
        try {
            sign = SignUtil.buildSign(params, this.secret);
        } catch (IOException e) {
            throw new SdkException("签名构建失败");
        }

        params.put(signName, sign);

        return params;
    }

    private String doPost(String url, String json, Map<String, String> header, List<UploadFile> files) {
        try {
            String body;
            if (files != null && files.size() > 0) {
                body = PostUtil.postFile(url, json, header, files);
            } else {
                body = PostUtil.postJson(url, json, header);
            }
            // 如果服务端丢失随机码，重新发起握手
            if (this.isMissRandomKey(body)) {
                this.handshake(this.url);

                return this.doPost(url, json, header, files);
            }
            // 对结果进行解密
            if (this.getRequestMode() == RequestMode.ENCRYPT) {
                body = AESUtil.decryptFromHex(body, randomKey);
            }
            return body;
        } catch (Exception e) {
            ErrorResp result = new ErrorResp();
            result.setCode("-1");
            result.setMsg(e.getMessage());
            return JsonUtil.toJson(result);
        }
    }

    private boolean isMissRandomKey(String body) {
        try {
            JSONObject jsonObj = JSON.parseObject(body);
            // 20表示服务端已经失去随机码，需要重新握手获取
            return "20".equals(jsonObj.getString("code"));
        } catch (Exception e) {
            return false;
        }
    }

    public String getLang() {
        return lang;
    }

    public void setLang(String lang) {
        this.lang = lang;
    }

    public RequestMode getRequestMode() {
        return RequestMode.SIGNATURE;
    }

}
